package de.hsb.ai.agent.analyzer;

import game.Move;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import de.hsb.ai.agent.AbstractDetective;
import de.hsb.ai.domain.GameOver;
import de.hsb.ai.domain.GameStarted;
import de.hsb.ai.domain.MoveEvent;
import de.hsb.ai.util.ScotlandJadeHelper;
import de.hsb.ai.util.map.ScotlandJadeMapHelper.ScotlandJadeEdge;
import de.hsb.ai.util.map.ScotlandJadeMapHelper.ScotlandJadeVertex;

/**
 * AbstractAnalyzerDetective provides common logic for both {@link AnalyzerMasterDetective}
 * and {@link AnalyzerSlaveDetective}. That is mainly path-finding logic
 * and such for coordination between the agents (see {@link #partners}).
 */
@SuppressWarnings("serial")
public abstract class AbstractAnalyzerDetective extends AbstractDetective {
	
	protected PathFinder pathFinder;
	
	private int wins;

	protected ScotlandJadePath currentPath;

	protected int currentPathOffset = 0;
	
	protected final List<AbstractAnalyzerDetective> partners = new LinkedList<AbstractAnalyzerDetective>();
	
	protected final Set<ScotlandJadeVertex> futurePartnerVertices = new HashSet<ScotlandJadeVertex>();
	
	public AbstractAnalyzerDetective(String serviceNamePrefix) {
		super(serviceNamePrefix);		
	}
	
	protected void addParnter(AbstractAnalyzerDetective partner) {
		if (!partners.contains(partner)) partners.add(partner);
	}
	
	protected void clearPartners() {
		partners.clear();
	}
	
	/**
	 * Called as soon one of the partners decides to go to a certain position.
	 * @param partner
	 * @param vertex
	 */
	protected void onPartnerGoesFor(AbstractAnalyzerDetective partner, ScotlandJadeVertex vertex) {
		futurePartnerVertices.add(vertex);
	}
	
	/**
	 * Called as soon one of the partners reached his target.
	 * @param partner
	 * @param vertex
	 */
	protected void onParnerReachedVertex(AbstractAnalyzerDetective partner, ScotlandJadeVertex vertex) {
		futurePartnerVertices.remove(vertex);
	}
	
	@Override
	protected void handleMove(MoveEvent m) {
		super.handleMove(m);
		
		ScotlandJadeVertex targetVertex = map.get(m.getMove().getNode());

		if (m.getPlayerIndex() == 0) {
			handleFugitiveMove(targetVertex, m.getMove().getType());
		} else {
			handleDetectiveMove(targetVertex, m.getMove().getType(), m.getPlayer().equals(getAID()));
		}

	}
	
	/**
	 * Called for every move done by a detective.
	 * @param targetVertex
	 * @param transport
	 * @param ownMove
	 */
	protected void handleDetectiveMove(ScotlandJadeVertex targetVertex, int transport, boolean ownMove) {
		if (ownMove) {
			pathFinder.update(positions.get(getPlayerIndex()), transport, getTickets(transport));
		}
	}

	/**
	 * Called for every move done by the fugitive.
	 * @param targetVertex
	 * @param type
	 */
	protected void handleFugitiveMove(ScotlandJadeVertex targetVertex, int type) {
		
	}

	@Override
	protected void handleGameStarted(GameStarted g) {
		super.handleGameStarted(g);
		
		pathFinder = new PathFinder(map);
		currentPath = null;
		currentPathOffset = 0;
		futurePartnerVertices.clear();
	}
	
	@Override
	protected void handleGameOver(GameOver g) {
		super.handleGameOver(g);
		
		if (g.getWinner().equals(getAID())) {
			++wins;
		}
	}
	
	int getWins() {
		return wins;
	}
	
	@Override
	protected Move getMove(List<Move> options) {
		boolean hasToRevealWithinTwoOrLessMoves = ScotlandJadeHelper.hasToRevealAt(getRound() + 2);
		hasToRevealWithinTwoOrLessMoves |= ScotlandJadeHelper.hasToRevealAt(getRound() + 1);
		
		// Basic behavior (see Phase A)
		if (hasToRevealWithinTwoOrLessMoves) {
			if (currentPath == null) {
				ScotlandJadeVertex position = positions.get(getPlayerIndex());
				pathFinder.setTemporailyUnavailable(futurePartnerVertices);
				currentPath = pathFinder.getPathToBestConnectedVertex(position);
				for (AbstractAnalyzerDetective partner : partners) {
					partner.onPartnerGoesFor(this, currentPath.getEndVertex());
				}
				logger.info("my path from " + position.getIndex() + ": " + currentPath);
			}
			return getMoveForEdge(options, currentPath.getEdgeList().get(currentPathOffset++));
		} else if (ScotlandJadeHelper.hasToRevealAt(getRound())) {
			// Close call! If the fugitive is standing right next to us jump at him
			ScotlandJadeEdge catchEdge = map.getEdge(positions.get(0), positions.get(getPlayerIndex()));
			if (catchEdge != null) {
				logger.info("SO CLOSE!");
				return getMoveForEdge(options, catchEdge);
			}
		}
		currentPath = null;
		currentPathOffset = 0;
		return null;
	}
	
	protected Move getMoveForEdge(List<Move> options, ScotlandJadeEdge edge) {
		ScotlandJadeVertex position = positions.get(getPlayerIndex());
		for (Move m : options) {
			if (m.getNode() == edge.getTargetIndex(position.getIndex()) && m.getType() == edge.getType()) {
				return m;
			}
		}
		logger.warn("no move found from " + position.getIndex() + " for " + edge);
		return null;
	}
	
	protected Move getMoveTowards(List<Move> options, int targetIndex) {
		ScotlandJadeVertex position = positions.get(getPlayerIndex());
		
		if (targetIndex < 1 || position.getIndex() == targetIndex) {
			return null;
		}
		
		List<ScotlandJadeEdge> _options = new ArrayList<ScotlandJadeEdge>(options.size());
		for (Move move : options) {
			_options.add(map.getEdge(position, map.get(move.getNode()), move.getType()));
		}
		if (_options.size() == 0) {
			logger.warn("failed to transform " + options + " moves into edges");
		} else {
			int offset = pathFinder.getNextMoveTowards(map.get(targetIndex), _options);
			if (offset >= 0) {
				ScotlandJadeEdge edge = _options.get(offset);
				logger.info("moving from " + position.getIndex() + " towards " + 
						targetIndex + " over " + 
						edge.getTargetIndex(position.getIndex()) + " by " + edge.getType());
				return options.get(offset);
			}
			logger.warn("no path found");
		}
		
		return null;
	}

}